

import {debugUtils} from "../debugUtils";
import geoUtils from '../utils/geoUtils';


// 相当于 flat 着色
class Graphics3d{
    constructor(imgData) {
        this.imgData = imgData;
        this.width = imgData.width;
        this.height = imgData.height;
        this.data = imgData.data;
        this.clamp = cc.math.clamp;

        // 是否开启深度缓存
        this.bDepthTest = true;

        // 深度缓存, 里面坐标和 逻辑坐标对应(左下角)
        this.depths = [];
        for(let i = 0; i < this.height;++i){
            this.depths[i] = new Array(this.width);
        }
    }

    _contains(x,y){
        return x >= 0 && x <= this.width - 1 && y >= 0 && y <= this.height - 1;
    }

    clear(){
        this.data.fill(0,0,this.data.length);

        // 清除深度缓存
        if(this.bDepthTest){
            this.depths.forEach(row=>{
                row.fill(1,0,row.length);
            });
        }
    }

    _pixel3d(x, y, z, color) {
        if(this.bDepthTest){
            if (z < this.depths[y][x]) {
                this.depths[y][x] = z;

                let ny = this.height - y - 1;
                let idx = ny * this.width * 4 + x * 4;
                this.data[idx] = color.r;
                this.data[idx + 1] = color.g;
                this.data[idx + 2] = color.b;
                this.data[idx + 3] = color.a;
            }
        }else{
            let ny = this.height - y - 1;
            let idx = ny * this.width * 4 + x * 4;
            this.data[idx] = color.r;
            this.data[idx + 1] = color.g;
            this.data[idx + 2] = color.b;
            this.data[idx + 3] = color.a;
        }
    }

    _drawLine3d(v0,v1,color){
        if (this._clipLine3d(v0, v1)) {
            this._line3d(v0, v1, color);
        }
    }

    _clipLine3d(v1, v2) {
        let x1 = v1.x, y1 = v1.y, x2 = v2.x, y2 = v2.y;
        const xMax = this.width - 1;
        const yMax = this.height - 1;
        const xMin = 0;
        const yMin = 0;

        if (debugUtils.debug) {
            debugUtils.checkInteger(x1);
            debugUtils.checkInteger(y1);
            debugUtils.checkInteger(x2);
            debugUtils.checkInteger(y2);
        }

        // 都在上 下 左 右 一定不裁剪
        if (y1 > yMax && y2 > yMax) {
            return false;
        }
        if (y1 < yMin && y2 < yMin) {
            return false;
        }
        if (x1 < xMin && x2 < xMin) {
            return false;
        }
        if (x1 > xMax && x2 > xMax) {
            return false;
        }

        if (this._contains(x1, y1) && this._contains(x2, y2)) {
            // 不需要裁剪, 取原来的值
            return true;
        }

        if (x1 === x2) {

            if (y1 === y2) {
                return false;
            }

            // 垂直
            let min_y = Math.min(y1, y2);
            let max_y = Math.max(y1, y2);
            if (min_y > yMax || max_y < yMin) {
                // 视口外
                return false;
            }
            let t_y1 = cc.math.clamp(y1, yMin, yMax);
            let t_y2 = cc.math.clamp(y2, yMin, yMax);

            let t1 = (t_y1 - v1.y) / (v2.y - v1.y);
            v1.z = (v2.z - v1.z) * t1 + v1.z;

            let t2 = (t_y2 - v2.y) / (v1.y - v2.y);
            v2.z = (v1.z - v2.z) * t2 + v2.z;

            v1.y = t_y1;
            v2.y = t_y2;
            return true;
        }

        if (y1 === y2) {

            if (x1 === x2) {
                return false;
            }

            // 水平
            let min_x = Math.min(x1, x2);
            let max_x = Math.max(x1, x2);
            if (min_x > xMax || max_x < xMin) {
                return false;
            }

            let t_x1 = cc.math.clamp(x1, xMin, xMax);
            let t_x2 = cc.math.clamp(x2, xMin, xMax);

            let t1 = (t_x1 - v1.x) / (v2.x - v1.x);
            v1.z = (v2.z - v1.z) * t1 + v1.z;

            let t2 = (t_x2 - v2.x) / (v1.x - v2.x);
            v2.z = (v1.z - v2.z) * t2 + v2.z;

            v1.x = t_x1;
            v2.x = t_x2;
            return true;
        }

        //
        let left_x = Math.min(x1, x2);
        let left_y, right_x, right_y;
        let flip; // 表示是否交换
        if (left_x === x1) {
            left_y = y1;
            right_x = x2;
            right_y = y2;
            flip = false;
        } else {
            left_y = y2;
            right_x = x1;
            right_y = y1;
            flip = true;
        }

        // 令 x 分别为 rect.x 和  rect.xMax
        // P = (1-t) * A + t * B
        // t = (P - A) / (B - A)
        let p1 = (xMin - left_x) / (right_x - left_x);
        let p2 = (xMax - left_x) / (right_x - left_x);

        // 令 y分别为 rect.y 和  rect.yMax
        let p3 = (yMin - left_y) / (right_y - left_y);
        let p4 = (yMax - left_y) / (right_y - left_y);

        let p_l = Math.max(0, p1, Math.min(p3, p4));
        let p_r = Math.min(1, p2, Math.max(p3, p4));

        let l_x = Math.floor((1 - p_l) * left_x + p_l * right_x);
        let l_y = Math.floor((1 - p_l) * left_y + p_l * right_y);
        let r_x = Math.floor((1 - p_r) * left_x + p_r * right_x);
        let r_y = Math.floor((1 - p_r) * left_y + p_r * right_y);

        if (l_x > xMax || r_x < xMin) {
            return false;
        }

        let ret_min_y = Math.min(l_y, r_y);
        let ret_max_y = Math.max(l_y, r_y);

        if (ret_min_y > yMax || ret_max_y < yMin) {
            return false;
        }

        // 由于浮点数的原因.....
        l_x = cc.math.clamp(l_x, xMin, xMax);
        r_x = cc.math.clamp(r_x, xMin, xMax);
        l_y = cc.math.clamp(l_y, yMin, yMax);
        r_y = cc.math.clamp(r_y, yMin, yMax);

        if (flip) {
            v1.x = r_x;
            v1.y = r_y;
            v2.x = l_x;
            v2.y = l_y;

            v1.z = (v2.z - v1.z) * p_r + v1.z;
            v2.z = (v2.z - v1.z) * p_l + v1.z;
        } else {
            v1.x = l_x;
            v1.y = l_y;
            v2.x = r_x;
            v2.y = r_y;

            v1.z = (v2.z - v1.z) * p_l + v1.z;
            v2.z = (v2.z - v1.z) * p_r + v1.z;
        }

        return true;
    }

    _line3d(v0,v1, color) {
        let x1 = v0.x,y1 = v0.y,z1 = v0.z;
        let x2 = v1.x,y2 = v1.y,z2 = v1.z;
        if (debugUtils.debug) {
            debugUtils.checkInteger(x1);
            debugUtils.checkInteger(y1);
            debugUtils.checkInteger(x2);
            debugUtils.checkInteger(y2);
        }

        // 不考虑裁剪
        if (x1 === x2) {
            // 垂直的
            let min = Math.min(y1, y2);
            let max = Math.max(y1, y2);
            let dis = max - min;

            let flip = (min === y2);
            let start_z = flip ? z2:z1;
            let end_z = flip? z1:z2;
            let dis_z = end_z - start_z;
            for (let i = min; i <= max; ++i) {
                let t = (i - min) / dis;
                let z = dis_z * t + start_z;
                this._pixel3d(x1, i,z, color);
            }
            return;
        }

        if (y1 === y2) {
            // 水平
            let min = Math.min(x1, x2);
            let max = Math.max(x1, x2);
            let dis = max - min;

            let flip = (min === x2);
            let start_z = flip? z2:z1;
            let end_z = flip? z1:z2;
            let dis_z = end_z - start_z;
            for (let i = min; i <= max; ++i) {
                let t = (i - min) / dis;
                let z = dis_z * t + start_z;
                this._pixel3d(i, y1,z, color);
            }
            return;
        }

        // left(start) -> right(end)
        let k = (y2 - y1) / (x2 - x1);
        let el = 0;
        let b = y1 - k * x1;
        if (k >= 1 || k <= -1) {
            let min_y = Math.min(y1, y2);
            let max_y = Math.max(y1, y2);
            let dis = max_y - min_y;
            let flip = (min_y === y2);
            let start_z = flip?z2:z1;
            let end_z = flip?z1:z2;
            let dis_z = end_z - start_z;
            for (let i = min_y; i <= max_y; ++i) {
                let nx = (i - b) / k;
                let ix = Math.floor(nx);
                el += (nx - ix);
                if (el >= 0.5) {
                    ix++;
                    el -= 1;
                }

                let t = (i - min_y) / dis;
                let z = dis_z * t + start_z;
                this._pixel3d(ix, i,z, color);
            }
        } else {
            let min_x = Math.min(x1, x2);
            let max_x = Math.max(x1, x2);
            let dis = max_x - min_x;
            let flip = (min_x === x2);
            let start_z = flip?z2:z1;
            let end_z = flip?z1:z2;
            let dis_z = end_z - start_z;
            for (let i = min_x; i <= max_x; ++i) {
                let ny = k * i + b;
                let iy = Math.floor(ny);
                el += (ny - iy);
                if (el >= 0.5) {
                    iy++;
                    el -= 1;
                }
                let t = (i - min_x) / dis;
                let z = dis_z * t + start_z;
                this._pixel3d(i, iy,z, color);
            }
        }
    }

    /**
     *
     * @param v0
     * @param v1
     * @param v2
     * @param color
     * @private
     */
    drawTriangle(v0,v1,v2,color){
        this._drawLine3d(v0,v1,color);
        this._drawLine3d(v1,v2,color);
        this._drawLine3d(v2,v0,color);
    }

    /**
     * v0 v1 v2 是逆时针的
     * @param v0
     * @param v1
     * @param v2
     * @param color
     */
    fillTriangle(v0,v1,v2,color){
        const xMax = this.width - 1;
        const yMax = this.height - 1;
        const zMax = 1;
        const zMin = -1;

        let x1 = v0.x,y1 = v0.y,z1 = v0.z;
        let x2 = v1.x,y2 = v1.y,z2 = v1.z;
        let x3 = v2.x,y3 = v2.y,z3 = v2.z;

        // 判断是否需要进行绘制???
        if ((x1 < 0 && x2 < 0 && x3 < 0) ||
            (x1 > xMax && x2 > xMax && x3 > xMax) ||
            (y1 < 0 && y2 < 0 && y3 < 0) ||
            (y1 > yMax && y2 > yMax && y3 > yMax) ||
            (z1 > zMax && z2 > zMax && z3 > zMax) ||
            (z1 < zMin && z2 < zMin && z3 < zMin)
        ) {
            return;
        }

        // 判断是否是逆时针. 如果顺时针需要调整顺序
        if (!geoUtils.isCCW(x1, y1, x2, y2, x3, y3)) {
            // 调整成逆时针
            // x2<->x3 互换
            let tx = x2;
            let ty = y2;
            let tz = z2;
            x2 = x3;
            y2 = y3;
            z2 = z3;
            x3 = tx;
            y3 = ty;
            z3 = tz;
        }


        // 调整顶点顺序
        if(y1 < y2 || y1 < y3){
            if(y2 < y3){
                let tx = x1;
                let ty = y1;
                let tz = z1;
                x1 = x3;
                y1 = y3;
                z1 = z3;
                x3 = x2;
                y3 = y2;
                z3 = z2;
                x2 = tx;
                y2 = ty;
                z2 = tz;
            }else{
                // y2 >= y3
                let tx = x1;
                let ty = y1;
                let tz = z1;
                x1 = x2;
                y1 = y2;
                z1 = z2;
                x2 = x3;
                y2 = y3;
                z2 = z3;
                x3 = tx;
                y3 = ty;
                z3 = tz;
            }
        }


        // 判断是否是平顶三角形
        if (y1 === y3) {
            this._fillTopFlatTriangle(x1,y1,z1,x2,y2,z2,x3,y3,z3,color);
        } else {
            // 判断是左边长还是右边长
            // 并且拆分成两个三角形
            if(y2  > y3){
                // 右边长. 求点D
                let yd = y2;
                let t = (yd - y1) / (y3 - y1);
                let xd = x1 + t * (x3 - x1);
                let zd = z1 + t * (z3 - z1);
                this._fillBottomFlatTriangle(x1,y1,z1,x2,y2,z2,Math.floor(xd),yd,zd,color);
                this._fillTopFlatTriangle(x2,y2,z2,x3,y3,z3,Math.floor(xd),yd,zd,color);
            }else{
                // 平底或左边长
                if(y2 === y3){
                    this._fillBottomFlatTriangle(x1,y1,z1,x2,y2,z2,x3,y3,z3,color);
                }else{
                    let yd = y3;
                    let t = (yd - y1) / (y2 - y1);
                    let xd = x1 + t * (x2 - x1);
                    let zd = z1 + t * (z2 - z1);
                    this._fillBottomFlatTriangle(x1,y1,z1,Math.floor(xd),yd,zd,x3,y3,z3,color);
                    this._fillTopFlatTriangle(Math.floor(xd),yd,zd,x2,y2,z2,x3,y3,z3,color);
                }
            }
        }
    }

    _fillTopFlatTriangle(x1,y1,z1,x2,y2,z2,x3,y3,z3,color){
        const xMax = this.width - 1;
        const yMax = this.height - 1;
        const zMax = 1;
        const zMin = -1;

        if (debugUtils.debug) {
            debugUtils.checkInteger(x1);
            debugUtils.checkInteger(y1);
            debugUtils.checkInteger(x2);
            debugUtils.checkInteger(y2);
            debugUtils.checkInteger(x3);
            debugUtils.checkInteger(y3);
        }
        let y_start = Math.min(yMax,y1);
        let y_end = Math.max(0,y2);
        let dis_left_x = x2 - x1;
        let dis_left_y = y2 - y1;
        let dis_right_x = x2 - x3;
        let dis_left_z = z2 - z1;
        let dis_right_z = z2 - z3;
        for(let j = y_start;j >= y_end; --j){
            let t = (j - y1) / dis_left_y;
            let x_start = x1 + dis_left_x * t;
            let x_end = x3 + dis_right_x * t;
            let z_start = z1 + dis_left_z * t;
            let z_end = z1 + dis_right_z * t;

            if(x_start > xMax || x_end < 0){
                continue;
            }

            // 为了避免裁剪
            let xs = this.clamp(x_start,0,xMax);
            let xe = this.clamp(x_end,0,xMax);

            //
            let dis3 = x_end - x_start;
            let t3 = (xs - x_start) / dis3;
            let t4 = (xe - x_start) / dis3;

            let zs = cc.math.lerp(z_start,z_end,t3);
            let ze = cc.math.lerp(z_start,z_end,t4);
            x_start = Math.floor(xs);
            x_end = Math.floor(xe);
            this._line3d(cc.v3(x_start,j,zs),cc.v3(x_end,j,ze),color);
        }
    }
    _fillBottomFlatTriangle(x1,y1,z1,x2,y2,z2,x3,y3,z3,color){
        let xMax = this.width - 1;
        let yMax = this.height - 1;
        let y_start = Math.min(yMax,y1);
        let y_end = Math.max(0,y2);
        let dis_left_x = x2 - x1;
        let dis_left_y = y2 - y1;
        let dis_right_x = x3 - x1;
        let dis_right_y = y3 - y1;
        let dis_left_z = z2 - z1;
        let dis_right_z = z3 - z1;

        //  先绘制上半部分
        for(let j = y_start; j >= y_end; --j){
            let t_left = (j - y1) / dis_left_y;
            let t_right = (j - y1) / dis_right_y;
            let x_start  = x1 + dis_left_x * t_left;
            let x_end = x1 + dis_right_x * t_right;
            let z_start = z1 + dis_left_z * t_left;
            let z_end = z1 + dis_right_z * t_right;

            if(x_start > xMax || x_end < 0){
                continue;
            }

            // 为了避免裁剪
            let xs = this.clamp(x_start,0,xMax);
            let xe = this.clamp(x_end,0,xMax);

            let dstx = x_end - x_start;
            let t3 = (xs - x_start) / dstx;
            let t4 = (xe - x_start) / dstx;

            let zs = cc.math.lerp(z_start,z_end,t3);
            let ze = cc.math.lerp(z_start,z_end,t4);

            x_start = Math.floor(xs);
            x_end = Math.floor(xe);

            this._line3d(cc.v3(x_start,j,zs),cc.v3(x_end,j,ze),color);
        }
    }

    // 绘制深度缓冲
    drawDepthBuffer(){
        // for test
        this.data.fill(0,0,this.data.length);

        this.bDepthTest = false;
        let tmpColor = cc.color();
        for(let i = 0; i < this.height;++i){
            for(let j = 0; j < this.width;++j){
                let x = this.depths[i][j];
                let t = (x + 1) / 2;
                // t = cc.math.clamp(t,0,1);
                cc.Color.lerp(tmpColor,cc.Color.BLACK,cc.Color.WHITE,t);
                this._pixel3d(j,i,0,tmpColor);
            }
        }
        this.bDepthTest = true;
    }
}

export default Graphics3d;
